﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint;

namespace Xodeuz.SP.Common
{
    public class ListItemReflector
    {
        /// <summary>
        /// Cast single list item to mapped object
        /// </summary>
        /// <returns></returns>
        public static T CastTo<T>(SPListItem item) where T : new()
        { 
            var type = typeof (T);

            var mapping = ReflectionService.GetPropertyNameWithCustomAttribute<Field>(type);

            if(mapping.Count == 0) throw new Exception("No mapping could be made to the type: " + type.Name);

            IEnumerable<PropertyInfo> properties = ReflectionService.PublicPropertyInfos(type);

            return CastObject<T>(mapping, properties, item);
        }

        /// <summary>
        /// Cast itemcollection to list of mapped objects
        /// </summary>
        /// <returns></returns>
        public static List<T> CastTo<T>(SPListItemCollection collection) where T : new()
        {
            var type = typeof (T);

            var mapping = ReflectionService.GetPropertyNameWithCustomAttribute<Field>(type);

            if(mapping.Count == 0) throw new Exception("No mapping could be made to the type: " + type.Name);

            IEnumerable<PropertyInfo> properties = ReflectionService.PublicPropertyInfos(type);

            return (from SPListItem listItem in collection select CastObject<T>(mapping, properties, listItem)).ToList();
        }

        /// <summary>
        /// Helper: Cast object to desired type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="mapping"></param>
        /// <param name="properties"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        private static T CastObject<T>(Dictionary<String, Field> mapping, IEnumerable<PropertyInfo> properties, SPListItem item ) where T : new()
        {
            var model = new T();
            try
            {
                foreach (var property in properties)
                {
                    // Skip if item doesn't contain field with the desired internal name
                    if (!mapping.ContainsKey(property.Name)) continue;

                    var fieldInfo = mapping[property.Name];
                    if (item.Fields.ContainsFieldWithStaticName(fieldInfo.InternalName))
                    {
                        var field = item.Fields.GetFieldByInternalName(fieldInfo.InternalName);
                        if (field.Type == SPFieldType.User)
                        {
                            var stringValue = (string) item[fieldInfo.InternalName];
                            var value = (SPFieldUserValue) field.GetFieldValue(stringValue);
                            if(value != null)
                                property.SetValue(model, value.ToString(), null);
                        }
                        else
                        {
                            if (item[fieldInfo.InternalName] != null)
                                property.SetValue(model, item[fieldInfo.InternalName], null);
                        }
                    }
                }

                EnsureOriginaData(typeof(T), item, model);
            }
            catch (ArgumentException)
            {
                throw new Exception("The field you have entered doesn't seem to exist or hasn't been loaded in - <fieldref>");
            }

            return model;
        }

        private static void EnsureOriginaData<T>(Type type, SPListItem item, T model) where T : new()
        {
            PropertyInfo siteIdProperty = ReflectionService.GetPropertyInfoByName(type, "SiteId");
            PropertyInfo webIdProperty = ReflectionService.GetPropertyInfoByName(type, "WebId");
            PropertyInfo listIdProperty = ReflectionService.GetPropertyInfoByName(type, "ListId"); 

            siteIdProperty.SetValue(model, item.Web.Site.ID, null);
            webIdProperty.SetValue(model, item.Web.ID, null);
            listIdProperty.SetValue(model, item.ParentList.ID, null);
        }
    }
}
